package labox.innovation.gameserver.network;

import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javolution.util.FastList;
import labox.innovation.config.Config;
import labox.innovation.db.PlayerDb;
import labox.innovation.db.PlayerDb.CharSelectInfoPackage;
import labox.innovation.gameserver.Engine;
import labox.innovation.gameserver.LoginServerThread;
import labox.innovation.gameserver.ThreadPoolManager; //import labox.innovation.gameserver.LoginServerThread.SessionKey;
import labox.innovation.gameserver.datatables.ClanTable;
import labox.innovation.gameserver.model.L2Clan;
import labox.innovation.gameserver.model.actor.instance.FuzePcInstance;
import labox.innovation.gameserver.model.entity.L2Event;
import labox.innovation.gameserver.network.serverpackets.CharSelectionInfo;
import labox.innovation.gameserver.network.serverpackets.GameGuardQuery;
import labox.innovation.gameserver.network.serverpackets.L2GameServerPacket;
import labox.innovation.gameserver.util.StoreIntervalRandom;
import labox.innovation.util.EventData;

import labox.innovation.netcore.MMOClient;
import labox.innovation.netcore.MMOConnection;
import labox.innovation.netcore.ReceivablePacket;

/**
 * Represents a client connected on Game Server
 * 
 * @author KenM
 */
public final class L2GameClient extends MMOClient<MMOConnection<L2GameClient>> {

	protected static final Logger _log = LoggerFactory.getLogger(L2GameClient.class.getName());

	/**
	 * CONNECTED - client has just connected AUTHED - client has authed but doesnt has character attached to it yet IN_GAME - client has selected a char and is in game
	 * 
	 * @author KenM
	 */
	public static enum GameClientState {
		CONNECTED, AUTHED, IN_GAME
	}

	public GameClientState state;

	/**
	 * 此客户端连接激活的账户
	 */
	private String _accountName;

	// private SessionKey _sessionId;

	private int _sessionId;

	/**
	 * 此客户端连接激活的玩家对象
	 */
	private volatile FuzePcInstance _activeChar;

	private boolean _isAuthedGG;

	private long _connectionStartTime;

	private List<CharSelectInfoPackage> _charSlotMapping;

	/**
	 * 定时回写数据库任务
	 */
	protected ScheduledFuture _autoSaveInDB;

	/**
	 * 定时发送心跳校验协议
	 */
	protected ScheduledFuture _queryGuard;

	/**
	 * 与客户端通讯的加密对象
	 */
	private GameCrypt _crypt;

	/**
	 * 是否与客户端已分离
	 */
	private volatile boolean _isDetached;

	private boolean _protocol;

	private int _count;

	private String _channelId;

	/**
	 * 客户端发送到服务器的协议对象队列
	 */
	private final ConcurrentLinkedQueue<ReceivablePacket<L2GameClient>> clientPacketQueue;

	public L2GameClient(MMOConnection<L2GameClient> con) {
		super(con);
		state = GameClientState.CONNECTED;
		_connectionStartTime = System.currentTimeMillis();
		_crypt = new GameCrypt();
		clientPacketQueue = new ConcurrentLinkedQueue<ReceivablePacket<L2GameClient>>();
		if (Config.CHAR_STORE_INTERVAL > 0) {
			int interval = StoreIntervalRandom.getInt(Config.CHAR_STORE_INTERVAL);
			//启动定时回写角色数据任务
			_autoSaveInDB = ThreadPoolManager.getInstance().scheduleWithFixedDelay(new Runnable() {
				public void run() {
					FuzePcInstance player = null;
					try {
						player = L2GameClient.this.getActiveChar();
						if (player != null) {
							saveCharToDisk(player);
							if (player.getPet() != null)
								player.getPet().store();
						}
					} catch (Exception e) {
						_log.error("Error on AutoSaveTask. player id:" + (player != null ? player.getObjectId() : ""), Config.SERVER_ID, e);
					}
				}
			}, interval * 60000, interval * 60000);
		}

		if (Config.GAMEGUARD_ENFORCE > 0) {
			_queryGuard = ThreadPoolManager.getInstance().scheduleWithFixedDelay(new Runnable() {
				public void run() {
					sendPacket(new GameGuardQuery());
				}
			}, Config.GAMEGUARD_ENFORCE * 1000, Config.GAMEGUARD_ENFORCE * 1000);
		}

	}

	/**
	 * 向协议通道增加一个协议包
	 * 
	 * @param packet
	 */
	public void addPacket(ReceivablePacket<L2GameClient> packet) {
		clientPacketQueue.offer(packet);
	}

	/**
	 * 从协议通道取得一个协议包
	 * 
	 * @return
	 */
	public ReceivablePacket<L2GameClient> getPacke() {
		return clientPacketQueue.poll();
	}

	public byte[] enableCrypt() {
		byte[] key = BlowFishKeygen.getRandomKey();
		_crypt.setKey(key);
		return key;
	}

	public GameClientState getState() {
		return state;
	}

	public void setState(GameClientState pState) {
		state = pState;
	}

	public long getConnectionStartTime() {
		return _connectionStartTime;
	}

	@Override
	public boolean decrypt(ByteBuffer buf, int size) {
		_crypt.decrypt(buf.array(), buf.position(), size);
		return true;
	}

	@Override
	public boolean encrypt(final ByteBuffer buf, final int size) {
		_crypt.encrypt(buf.array(), buf.position(), size);
		buf.position(buf.position() + size);
		return true;
	}

	public FuzePcInstance getActiveChar() {
		return _activeChar;
	}

	public void setActiveChar(FuzePcInstance pActiveChar) {
		_activeChar = pActiveChar;
	}

	public void setGameGuardOk(boolean val) {
		_isAuthedGG = val;
	}

	public boolean isAuthedGG() {
		return _isAuthedGG;
	}

	/**
	 * 设置此客户端模型维护的账户名
	 * 
	 * @param pAccountName
	 */
	public void setAccountName(String pAccountName) {
		_accountName = pAccountName;
	}

	/**
	 * 获得此客户端模型维护的账户名
	 * 
	 * @return
	 */
	public String getAccountName() {
		return _accountName;
	}

	public void setSessionId(int sk) {
		_sessionId = sk;
	}

	public int getSessionId() {
		return _sessionId;
	}

	public void sendPacket(L2GameServerPacket gsp) {
		if (_isDetached)
			return;

		// 如果一个包被标记位invisible则只发送给GM
		if (gsp.isInvisible() && getActiveChar() != null && !getActiveChar().isGM())
			return;

		getConnection().sendPacket(gsp);
		gsp.runImpl();
	}

	/**
	 * 玩家是否与此GameClient分离
	 * 
	 * @return
	 */
	public boolean isDetached() {
		return _isDetached;
	}

	/**
	 * 设置玩家端与GameClient分离
	 * 
	 * @param b
	 */
	public void isDetached(boolean b) {
		_isDetached = b;
	}

	/**
	 * 标记角色为删除状态
	 * 
	 * @return a byte: <li>-1: Error: No char was found for such charslot, caught exception, etc... <li>0: character is not member of any clan, proceed with deletion <li>1: character is member of a clan, but not clan leader <li>2: character is clan leader
	 */
	public byte markToDeleteChar(int charslot) {
		CharSelectInfoPackage csip = getObjectIdForSlot(charslot);
		if (csip == null)
			return -1;
		if (csip.getDeleteTimer() > 0)
			return -1;
		long objid = csip.getObjectId();
		if (objid < 0)
			return -1;

		//获得帮派的ID
		long clanId = PlayerDb.markToDeleteCharSelect(objid);
		byte answer = 0;
		if (clanId != 0) {
			L2Clan clan = ClanTable.getInstance().getClan(clanId);

			if (clan == null)//如果帮派不存在
				answer = 0; // jeezes!
			else if (clan.getLeaderId() == objid)//如果是帮派的头领
				answer = 2;
			else
				answer = 1;//如果是普通的成员
		}

		// Setting delete time
		if (answer == 0) {
			if (Config.DELETE_DAYS == 0) {//如果设置延迟删除的时间是0，则直接删除
				PlayerDb.deleteCharByObjId(objid);
				if (getAccountName() != null)
					LoginServerThread.getInstance().sendCharacterCounts(getAccountName());
			} else {
				PlayerDb.updateCharacterDeletetime(objid, System.currentTimeMillis() + Config.DELETE_DAYS * 86400000L);//24*60*60*1000=86400000L
			}
			_log.warn("标记删除角色:" + objid + " L2GameClient:" + L2GameClient.this, objid);
		}

		return answer;

	}

	/**
	 * Save the L2PcInstance to the database.
	 */
	public static void saveCharToDisk(FuzePcInstance cha) {
		try {
			cha.store();
		} catch (Exception e) {
			_log.error("Error saving character", cha.getObjectId(), e);
		}
	}

	/**
	 * 恢复角色
	 * 
	 * @param charslot
	 * @throws Exception
	 */
	public void markRestoredChar(int charslot) throws Exception {
		// have to make sure active character must be nulled
		/*
		 * if (getActiveChar() != null) { saveCharToDisk (getActiveChar()); if
		 * (Config.DEBUG) _log.fine("active Char saved");
		 * this.setActiveChar(null); }
		 */

		CharSelectInfoPackage csip = getObjectIdForSlot(charslot);
		if (csip == null)
			return;
		if (csip.getDeleteTimer() == 0)
			return;

		long objid = csip.getObjectId();
		if (objid < 0)
			return;

		PlayerDb.updateCharacterDeletetime(objid, 0);
		_log.warn("恢复角色:" + objid + " L2GameClient:" + L2GameClient.this, objid);
	}

	public FuzePcInstance loadCharFromDisk(int charslot) {
		CharSelectInfoPackage csip = getObjectIdForSlot(charslot);
		if (csip == null)
			return null;

		if (csip.getDeleteTimer() > 0)
			return null;

		FuzePcInstance character = FuzePcInstance.load(csip.getObjectId());

		if (character != null) {
			character.setOnlineStatus(true);
		} else {
			_log.error("could not restore in slot: " + charslot + "  IP: " + getConnection().getInetAddress().getHostAddress(), Config.SERVER_ID, "请求的位置没角色");
		}

		// setCharacter(character);
		return character;
	}

	/**
	 * @param chars
	 */
	public void setCharSelection(List<CharSelectInfoPackage> chars) {
		_charSlotMapping = chars;
	}

	public void close(L2GameServerPacket gsp) {
		getConnection().close(gsp);
	}

	/**
	 * @param charslot
	 * @return
	 */
	private CharSelectInfoPackage getObjectIdForSlot(int charslot) {
		if (charslot < 0 || charslot >= _charSlotMapping.size()) {
			_log.warn(toString() + " tried to deal Character in slot " + charslot + " but no characters exits at that slot.", Config.SERVER_ID, "请求的位置没角色");
			return null;
		}
		return _charSlotMapping.get(charslot);
	}

	/**
	 * 网络连接底层传上来的客户端强断事件,注意此方法的执行是由底层selectorThread进行的，要尽量保证方法的快速返回
	 */
	@Override
	public void onForcedDisconnection() {
		if (_log.isDebugEnabled())
			_log.debug("Disconnected abnormally");
		// 客户端分离了
		isDetached(true);
		// 如果客户端模型已经链接有玩家对象，则请求主线程登出玩家对象
		if (_activeChar != null) {
			Engine.getInstance().addTask(new Runnable() {
				public void run() {
					FuzePcInstance activeChar;
					if ((activeChar = _activeChar) != null)
						activeChar.logout(false);
				}
			});
		} else {
			ThreadPoolManager.getInstance().execute(new Runnable() {
				public void run() {
					if (_autoSaveInDB != null) {// 如果没有玩家对象立刻停止数据库更新定时器
						_autoSaveInDB.cancel(true);
						_autoSaveInDB = null;
					}
					if (_queryGuard != null) {
						_queryGuard.cancel(true);
						_queryGuard = null;
					}
					//如果没有角色登陆，客户端可能处在角色选择状态，这时如果客户端主动的断掉连接，需要释放这个账户及客户端
					LoginServerThread.getInstance().sendLogout(getAccountName());
				}
			});

		}
	}

	/**
	 * 网络连接底层传上来的服务器断开事件
	 */
	@Override
	public void onDisconnection() {
		// 客户端分离了
		isDetached(true);
		ThreadPoolManager.getInstance().execute(new DisconnectTask(false));
	}

	/**
	 * 获得角色列表
	 */
	public void RegetCharList() {
		ThreadPoolManager.getInstance().execute(new DisconnectTask(true));
	}

	/**
	 * 立刻关闭当前的连接,强迫登出游戏服务器
	 */
	public void closeNow() {
		if (_activeChar != null) {
			Engine.getInstance().addTask(new Runnable() {
				public void run() {
					FuzePcInstance activeChar;
					if ((activeChar = _activeChar) != null)
						activeChar.logout(false);
				}
			});
		} else {
			close(null);
		}

	}

	/**
	 * 玩家离线处理任务
	 * 
	 * @author Administrator
	 * 
	 */
	class DisconnectTask implements Runnable {

		private boolean _getCharList;

		public DisconnectTask(boolean getCharList) {
			_getCharList = getCharList;
		}

		public void run() {
			boolean fast = true;
			try {
				FuzePcInstance player = L2GameClient.this.getActiveChar();
				if (player != null) {
					if (player.isInCombat()) {
						fast = false;
					}
				}

				ThreadPoolManager.getInstance().schedule(new CleanupTask(_getCharList), fast ? 5 : 15000);
			} catch (Exception e1) {
				_log.error("Error while disconnecting client.", Config.SERVER_ID, e1);
			}

		}

	}

	/**
	 * Produces the best possible string representation of this client.
	 */
	@Override
	public String toString() {
		try {
			InetAddress address = getConnection().getInetAddress();
			switch (getState()) {
			case CONNECTED:
				return "[IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
			case AUTHED:
				return "[Account: " + getAccountName() + " - IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
			case IN_GAME:
				return "[Character: " + (getActiveChar() == null ? "disconnected" : getActiveChar().getName()) + " - Account: " + getAccountName() + " - IP: " + (address == null ? "disconnected" : address.getHostAddress()) + "]";
			default:
				throw new IllegalStateException("Missing state on switch");
			}
		} catch (NullPointerException e) {
			return "[Character read failed due to disconnect]";
		}
	}

	class CleanupTask implements Runnable {

		/**
		 * 是否获得角色列表
		 */
		private boolean _getCharList;

		public CleanupTask(boolean sendLogin) {
			_getCharList = sendLogin;
		}

		public void run() {
			try {

				FuzePcInstance player = L2GameClient.this.getActiveChar();
				
				setActiveChar(null);
				// we are going to manually save the char bellow thus we can force the cancel
				if (!_getCharList && _autoSaveInDB != null) {
					_autoSaveInDB.cancel(false);
					_autoSaveInDB = null;
				}

				if (!_getCharList && _queryGuard != null) {
					_queryGuard.cancel(true);
					_queryGuard = null;
				}

				if (player != null) { // this should only happen on connection loss
					player.setClient(null);
					// we store all data from players who are disconnected while in an event in order to restore it in the next login
					if (player.atEvent) {
						EventData data = new EventData(player.eventX, player.eventY, player.eventkarma, player.eventpkkills, player.eventTitle, player.kills, player.eventSitForced);
						L2Event.connectionLossData.put(player.getName(), data);
					}

					//					player.storePetFood();

					try {
						saveCharToDisk(player);
					} catch (Exception e2) { /* ignore any problems here */
					}
				}

				if (_getCharList) {
					clientPacketQueue.clear();
					setState(GameClientState.AUTHED);//设置客户端进入已授权状态
					CharSelectionInfo cl = new CharSelectionInfo(getAccountName(), getSessionId());
					sendPacket(cl);//发送给客户端角色选择状态
					setCharSelection(cl.getCharInfo());//设置客户端的选择位置map
				}

			} catch (Exception e1) {
				_log.error("Error while cleanup client.", Config.SERVER_ID, e1);
			} finally {
				if (!_getCharList)
					LoginServerThread.getInstance().sendLogout(L2GameClient.this.getAccountName());
			}
		}
	}

	/**
	 * 获得客户端的协议情况
	 * 
	 * @return
	 */
	public boolean isProtocolOk() {
		return _protocol;
	}

	/**
	 * 设置客户端的协议情况
	 * 
	 * @return
	 */
	public void setProtocolOk(boolean b) {
		_protocol = b;
	}

	public void setGuardCount(int count) {
		_count = count;
	}

	public int getGuardCount() {
		return _count;
	}

	/**
	 * 设置此客户端的渠道ID
	 * 
	 * @param channelId
	 */
	public void setChannelId(String channelId) {
		_channelId = channelId;
	}

	/**
	 * 获取客户端得渠道ID
	 * 
	 * @return
	 */
	public String getChannelId() {
		return _channelId;
	}
}
